@rubytech/create-realagent 1.0.678 → 1.0.681

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/dist/index.js +232 -39
  2. package/package.json +1 -1
  3. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts +2 -0
  4. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.d.ts.map +1 -0
  5. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js +112 -0
  6. package/payload/platform/lib/graph-mcp/dist/__tests__/cypher-validate.test.js.map +1 -0
  7. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts +2 -0
  8. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.d.ts.map +1 -0
  9. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js +163 -0
  10. package/payload/platform/lib/graph-mcp/dist/__tests__/schema-cache.test.js.map +1 -0
  11. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts +38 -0
  12. package/payload/platform/lib/graph-mcp/dist/cypher-validate.d.ts.map +1 -0
  13. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js +130 -0
  14. package/payload/platform/lib/graph-mcp/dist/cypher-validate.js.map +1 -0
  15. package/payload/platform/lib/graph-mcp/dist/index.js +201 -45
  16. package/payload/platform/lib/graph-mcp/dist/index.js.map +1 -1
  17. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts +78 -0
  18. package/payload/platform/lib/graph-mcp/dist/schema-cache.d.ts.map +1 -0
  19. package/payload/platform/lib/graph-mcp/dist/schema-cache.js +194 -0
  20. package/payload/platform/lib/graph-mcp/dist/schema-cache.js.map +1 -0
  21. package/payload/platform/lib/graph-mcp/src/__tests__/cypher-validate.test.ts +141 -0
  22. package/payload/platform/lib/graph-mcp/src/__tests__/schema-cache.test.ts +169 -0
  23. package/payload/platform/lib/graph-mcp/src/cypher-validate.ts +157 -0
  24. package/payload/platform/lib/graph-mcp/src/index.ts +247 -47
  25. package/payload/platform/lib/graph-mcp/src/schema-cache.ts +212 -0
  26. package/payload/platform/lib/graph-trash/dist/index.d.ts +8 -0
  27. package/payload/platform/lib/graph-trash/dist/index.d.ts.map +1 -1
  28. package/payload/platform/lib/graph-trash/dist/index.js +109 -14
  29. package/payload/platform/lib/graph-trash/dist/index.js.map +1 -1
  30. package/payload/platform/lib/graph-trash/src/index.ts +136 -21
  31. package/payload/platform/plugins/docs/references/deployment.md +4 -2
  32. package/payload/platform/plugins/docs/references/memory-guide.md +5 -1
  33. package/payload/platform/plugins/docs/references/platform.md +1 -1
  34. package/payload/platform/plugins/docs/references/troubleshooting.md +20 -0
  35. package/payload/platform/plugins/memory/PLUGIN.md +1 -0
  36. package/payload/platform/plugins/memory/mcp/dist/index.js +54 -6
  37. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  38. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts +36 -0
  39. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.d.ts.map +1 -0
  40. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js +86 -0
  41. package/payload/platform/plugins/memory/mcp/dist/lib/filter-token.js.map +1 -0
  42. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts +23 -0
  43. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.d.ts.map +1 -1
  44. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js +47 -1
  45. package/payload/platform/plugins/memory/mcp/dist/tools/memory-delete.js.map +1 -1
  46. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts +58 -0
  47. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.d.ts.map +1 -0
  48. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js +125 -0
  49. package/payload/platform/plugins/memory/mcp/dist/tools/memory-find-candidates.js.map +1 -0
  50. package/payload/platform/scripts/vnc.sh +12 -409
  51. package/payload/platform/templates/agents/admin/IDENTITY.md +16 -0
  52. package/payload/platform/templates/dotfiles/.tmux.conf +1 -0
  53. package/payload/platform/templates/systemd/maxy-ttyd.service +25 -0
  54. package/payload/server/chunk-3RBKKDHC.js +783 -0
  55. package/payload/server/maxy-edge.js +377 -8
  56. package/payload/server/public/assets/admin-CIkyOur7.js +362 -0
  57. package/payload/server/public/assets/admin-kHJ-D0s7.css +1 -0
  58. package/payload/server/public/index.html +2 -1
  59. package/payload/server/server.js +391 -412
  60. package/payload/server/public/assets/admin-BBL1no_g.js +0 -352
package/dist/index.js CHANGED
@@ -2,7 +2,8 @@
2
2
  import { execFileSync, spawn, spawnSync } from "node:child_process";
3
3
  import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync, rmSync, readdirSync, appendFileSync, openSync, closeSync, chmodSync, symlinkSync, unlinkSync, lstatSync, readlinkSync, accessSync, constants as fsConstants } from "node:fs";
4
4
  import { resolve, join, dirname } from "node:path";
5
- import { randomBytes } from "node:crypto";
5
+ import { randomBytes, createHash } from "node:crypto";
6
+ import { TTYD_VERSION, TTYD_SHA256_BY_ARCH, mapUnameToTtydArch, ttydDownloadUrl, } from "./pinned-binaries.js";
6
7
  const PAYLOAD_DIR = resolve(import.meta.dirname, "../payload");
7
8
  // Brand manifest — read from payload to derive all brand-specific installation values.
8
9
  // The bundler stamps brand.json into the payload at build time.
@@ -332,6 +333,7 @@ function pkgsMissing(pkgs) {
332
333
  function installAptGroup(label, pkgs) {
333
334
  const pairs = pkgs.map((original) => ({ original, resolved: resolveAptName(original) }));
334
335
  logFile(` apt install (${label}): ${pairs.map((x) => x.resolved).join(" ")}`);
336
+ console.log(" [privileged] apt-get install");
335
337
  shell("apt-get", ["install", "-y", ...pairs.map((x) => x.resolved)], { sudo: true });
336
338
  const stillMissing = pairs.filter(({ resolved }) => {
337
339
  const r = spawnSync("dpkg", ["-s", resolved], { stdio: "pipe", timeout: 5_000 });
@@ -364,8 +366,12 @@ function installSystemDeps() {
364
366
  // assertion in vnc.sh check_window_on_display, closing the silent-fail
365
367
  // class where PID is alive but no window is mapped on the target display.
366
368
  const VNC_DEPS = ["tigervnc-standalone-server", "python3-websockify", "novnc", "xdg-utils", "chromium", "xterm", "xdotool"];
369
+ // Task 657: tmux powers the byte-stream admin terminal. ttyd attaches the
370
+ // shared `maxy-pty` tmux session, so scrollback survives WS reconnects and
371
+ // the same session is reused by the header overlay + upgrade modal.
372
+ const TERMINAL_DEPS = ["tmux"];
367
373
  const WIFI_DEPS = ["hostapd", "dnsmasq"];
368
- const ALL_APT_DEPS = [...BASE_DEPS, ...VNC_DEPS, ...WIFI_DEPS];
374
+ const ALL_APT_DEPS = [...BASE_DEPS, ...VNC_DEPS, ...TERMINAL_DEPS, ...WIFI_DEPS];
369
375
  // Task 634 — verify the "deps are present" assumption with `dpkg -s` instead
370
376
  // of asserting it (feedback_loud_failures.md). The previous silent-skip
371
377
  // branch was benign until Task 632 added xdotool (the first new apt dep
@@ -388,6 +394,7 @@ function installSystemDeps() {
388
394
  }
389
395
  console.log(` Missing apt deps (${missing.length}): ${missing.join(", ")}`);
390
396
  console.log(` Installing via sudo apt-get — sudo may prompt for your password...`);
397
+ console.log(" [privileged] apt-get update");
391
398
  shell("apt-get", ["update"], { sudo: true });
392
399
  installAptGroup("base utilities", BASE_DEPS);
393
400
  installAptGroup("VNC stack", VNC_DEPS);
@@ -402,9 +409,12 @@ function installSystemDeps() {
402
409
  // --hostname flag: set unconditionally, no detection, no preservation logic.
403
410
  console.log(` Hostname: ${HOSTNAME_FLAG} (from --hostname flag)`);
404
411
  try {
412
+ console.log(" [privileged] hostnamectl set-hostname");
405
413
  shell("hostnamectl", ["set-hostname", HOSTNAME_FLAG], { sudo: true });
414
+ console.log(" [privileged] sed -i");
406
415
  shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\t${HOSTNAME_FLAG}/`, "/etc/hosts"], { sudo: true });
407
416
  try {
417
+ console.log(" [privileged] sed -i");
408
418
  shell("sed", ["-i", `s/^[#]*host-name=.*/host-name=${HOSTNAME_FLAG}/`, "/etc/avahi/avahi-daemon.conf"], { sudo: true });
409
419
  console.log(` Avahi host-name: ${HOSTNAME_FLAG} (updated avahi-daemon.conf)`);
410
420
  }
@@ -455,9 +465,12 @@ function installSystemDeps() {
455
465
  console.log(` Hostname: ${BRAND.hostname} (${reason})`);
456
466
  hostnameSetAttempted = true;
457
467
  try {
468
+ console.log(" [privileged] hostnamectl set-hostname");
458
469
  shell("hostnamectl", ["set-hostname", BRAND.hostname], { sudo: true });
470
+ console.log(" [privileged] sed -i");
459
471
  shell("sed", ["-i", `s/127\\.0\\.1\\.1.*$/127.0.1.1\\t${BRAND.hostname}/`, "/etc/hosts"], { sudo: true });
460
472
  try {
473
+ console.log(" [privileged] sed -i");
461
474
  shell("sed", ["-i", `s/^[#]*host-name=.*/host-name=${BRAND.hostname}/`, "/etc/avahi/avahi-daemon.conf"], { sudo: true });
462
475
  console.log(` Avahi host-name: ${BRAND.hostname} (updated avahi-daemon.conf)`);
463
476
  }
@@ -500,8 +513,11 @@ function installSystemDeps() {
500
513
  const avahiDestPath = `/etc/avahi/services/${BRAND.hostname}.service`;
501
514
  try {
502
515
  writeFileSync(avahiTmpPath, avahiService);
516
+ console.log(" [privileged] cp");
503
517
  shell("cp", [avahiTmpPath, avahiDestPath], { sudo: true });
518
+ console.log(" [privileged] systemctl enable");
504
519
  shell("systemctl", ["enable", "avahi-daemon"], { sudo: true });
520
+ console.log(" [privileged] systemctl restart");
505
521
  shell("systemctl", ["restart", "avahi-daemon"], { sudo: true });
506
522
  }
507
523
  catch { /* not critical */ }
@@ -532,6 +548,7 @@ function installSystemDeps() {
532
548
  if (existsSync("/usr/bin/nmcli") && !existsSync(nmConfFile)) {
533
549
  console.log(" Disabling WiFi power save...");
534
550
  writeFileSync(`/tmp/${BRAND.hostname}-no-powersave.conf`, "[connection]\nwifi.powersave = 2\n");
551
+ console.log(" [privileged] cp");
535
552
  shell("cp", [`/tmp/${BRAND.hostname}-no-powersave.conf`, nmConfFile], { sudo: true });
536
553
  spawnSync("sudo", ["systemctl", "restart", "NetworkManager"], { stdio: "pipe" });
537
554
  }
@@ -549,6 +566,7 @@ function installNodejs() {
549
566
  throw new Error("Automatic Node.js installation is only supported on Linux. Install Node.js 20+ manually.");
550
567
  }
551
568
  spawnSync("bash", ["-c", "curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -"], { stdio: "inherit" });
569
+ console.log(" [privileged] apt-get install");
552
570
  shell("apt-get", ["install", "-y", "nodejs"], { sudo: true });
553
571
  }
554
572
  function installClaudeCode() {
@@ -592,6 +610,7 @@ function installClaudeCode() {
592
610
  }
593
611
  else {
594
612
  console.log(" This may take 15–30 minutes on Raspberry Pi...");
613
+ console.log(" [privileged] npm install -g @anthropic-ai/claude-code@latest");
595
614
  shellRetry("npm", ["install", "-g", ...NPM_NET_FLAGS, "--loglevel", "verbose", "@anthropic-ai/claude-code@latest"], { sudo: true, timeout: 2_400_000 }, // 40 min — Pi downloads can take 25+ min
596
615
  3, 30);
597
616
  }
@@ -668,8 +687,10 @@ function resetNeo4jAuth(port = DEFAULT_NEO4J_PORT, dataDir = "/var/lib/neo4j") {
668
687
  });
669
688
  }
670
689
  else {
690
+ console.log(" [privileged] neo4j-admin dbms");
671
691
  shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { sudo: true });
672
692
  }
693
+ console.log(" [privileged] systemctl start");
673
694
  shell("systemctl", ["start", serviceName], { sudo: true });
674
695
  console.log(" Waiting for Neo4j to start...");
675
696
  for (let i = 0; i < 15; i++) {
@@ -797,11 +818,15 @@ function installNeo4j() {
797
818
  const has17 = policyResult.status === 0 && !policyOutput.includes("Candidate: (none)");
798
819
  const javaPackage = has17 ? "openjdk-17-jre-headless" : "openjdk-21-jre-headless";
799
820
  console.log(` Installing Java (${javaPackage})...`);
821
+ console.log(" [privileged] apt-get install");
800
822
  shell("apt-get", ["install", "-y", javaPackage], { sudo: true });
801
823
  spawnSync("bash", ["-c", "curl -fsSL https://debian.neo4j.com/neotechnology.gpg.key | sudo gpg --yes --dearmor -o /usr/share/keyrings/neo4j.gpg 2>/dev/null"], { stdio: "inherit" });
802
824
  spawnSync("bash", ["-c", 'echo "deb [signed-by=/usr/share/keyrings/neo4j.gpg] https://debian.neo4j.com stable 5" | sudo tee /etc/apt/sources.list.d/neo4j.list'], { stdio: "inherit" });
825
+ console.log(" [privileged] apt-get update");
803
826
  shell("apt-get", ["update"], { sudo: true });
827
+ console.log(" [privileged] apt-get install");
804
828
  shell("apt-get", ["install", "-y", "neo4j"], { sudo: true });
829
+ console.log(" [privileged] sed -i");
805
830
  shell("sed", ["-i", "s/#server.default_listen_address=0.0.0.0/server.default_listen_address=127.0.0.1/", "/etc/neo4j/neo4j.conf"], { sudo: true });
806
831
  // Generate strong random password — stored in persistent location (~/{configDir}/)
807
832
  const password = randomBytes(24).toString("base64url");
@@ -812,8 +837,11 @@ function installNeo4j() {
812
837
  const configDir = resolve(INSTALL_DIR, "platform/config");
813
838
  mkdirSync(configDir, { recursive: true });
814
839
  writeFileSync(join(configDir, ".neo4j-password"), password, { mode: 0o600 });
840
+ console.log(" [privileged] neo4j-admin dbms");
815
841
  shell("neo4j-admin", ["dbms", "set-initial-password", "--", password], { sudo: true });
842
+ console.log(" [privileged] systemctl enable");
816
843
  shell("systemctl", ["enable", "neo4j"], { sudo: true });
844
+ console.log(" [privileged] systemctl start");
817
845
  shell("systemctl", ["start", "neo4j"], { sudo: true });
818
846
  console.log(" Neo4j started. Password stored securely.");
819
847
  }
@@ -851,11 +879,16 @@ function setupDedicatedNeo4j() {
851
879
  throw new Error("/etc/neo4j/neo4j.conf not found. Cannot create dedicated instance without base config.");
852
880
  }
853
881
  // 1. Copy base config
882
+ console.log(" [privileged] cp -r");
854
883
  shell("cp", ["-r", "/etc/neo4j", confDir], { sudo: true });
855
884
  // 2. Modify config for this instance: bolt port, HTTP port, data/log directories
885
+ console.log(" [privileged] sed -i");
856
886
  shell("sed", ["-i", `s/^#\\?server\\.bolt\\.listen_address=.*/server.bolt.listen_address=:${NEO4J_PORT}/`, `${confDir}/neo4j.conf`], { sudo: true });
887
+ console.log(" [privileged] sed -i");
857
888
  shell("sed", ["-i", `s/^#\\?server\\.http\\.listen_address=.*/server.http.listen_address=:${httpPort}/`, `${confDir}/neo4j.conf`], { sudo: true });
889
+ console.log(" [privileged] sed -i");
858
890
  shell("sed", ["-i", `s|^#\\?server\\.directories\\.data=.*|server.directories.data=${dataDir}/data|`, `${confDir}/neo4j.conf`], { sudo: true });
891
+ console.log(" [privileged] sed -i");
859
892
  shell("sed", ["-i", `s|^#\\?server\\.directories\\.logs=.*|server.directories.logs=${logDir}|`, `${confDir}/neo4j.conf`], { sudo: true });
860
893
  // Verify config was updated — sed silently no-ops if the key format changed
861
894
  const confContent = spawnSync("grep", [`server.bolt.listen_address=:${NEO4J_PORT}`, `${confDir}/neo4j.conf`], { stdio: "pipe" });
@@ -864,7 +897,9 @@ function setupDedicatedNeo4j() {
864
897
  logFile(` WARNING: sed verification failed — bolt port ${NEO4J_PORT} not found in ${confDir}/neo4j.conf`);
865
898
  }
866
899
  // 3. Create data and log directories
900
+ console.log(" [privileged] mkdir -p");
867
901
  shell("mkdir", ["-p", `${dataDir}/data`, logDir], { sudo: true });
902
+ console.log(" [privileged] chown -R");
868
903
  shell("chown", ["-R", "neo4j:neo4j", dataDir, logDir, confDir], { sudo: true });
869
904
  // 4. Create systemd service
870
905
  const serviceContent = `[Unit]
@@ -885,6 +920,7 @@ WantedBy=multi-user.target
885
920
  `;
886
921
  const tmpServicePath = `/tmp/${serviceName}.service`;
887
922
  writeFileSync(tmpServicePath, serviceContent);
923
+ console.log(" [privileged] cp");
888
924
  shell("cp", [tmpServicePath, `/etc/systemd/system/${serviceName}.service`], { sudo: true });
889
925
  spawnSync("rm", ["-f", tmpServicePath]);
890
926
  // 5. Set initial password before first start
@@ -901,7 +937,9 @@ WantedBy=multi-user.target
901
937
  });
902
938
  // 6. Enable and start the dedicated service
903
939
  spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
940
+ console.log(" [privileged] systemctl enable");
904
941
  shell("systemctl", ["enable", serviceName], { sudo: true });
942
+ console.log(" [privileged] systemctl start");
905
943
  shell("systemctl", ["start", serviceName], { sudo: true });
906
944
  // 7. Verify connectivity — poll until cypher-shell can connect
907
945
  console.log(` Waiting for dedicated Neo4j instance on port ${NEO4J_PORT}...`);
@@ -1046,6 +1084,7 @@ function installCloudflared() {
1046
1084
  const arch = isArm64() ? "arm64" : "amd64";
1047
1085
  const debPath = "/tmp/cloudflared.deb";
1048
1086
  shellRetry("curl", ["-fSL", "--progress-bar", `https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-${arch}.deb`, "-o", debPath], { timeout: 120_000 }, 3, 10);
1087
+ console.log(" [privileged] dpkg -i");
1049
1088
  shell("dpkg", ["-i", debPath], { sudo: true });
1050
1089
  spawnSync("rm", ["-f", debPath]);
1051
1090
  }
@@ -1063,23 +1102,50 @@ function installWhisperCpp() {
1063
1102
  return;
1064
1103
  }
1065
1104
  // Build dependencies — cmake is required since whisper.cpp migrated from plain make
1105
+ console.log(" [privileged] apt-get install");
1066
1106
  shell("apt-get", ["install", "-y", "build-essential", "cmake"], { sudo: true });
1067
1107
  // Clone or update the repository
1068
1108
  if (!existsSync(WHISPER_DIR)) {
1069
1109
  console.log(" Cloning whisper.cpp...");
1110
+ console.log(" [privileged] git clone");
1070
1111
  shell("git", ["clone", "--depth", "1", "https://github.com/ggerganov/whisper.cpp.git", WHISPER_DIR], { sudo: true });
1071
1112
  }
1072
1113
  // Compile via cmake (whisper.cpp's Makefile is a thin cmake wrapper)
1073
1114
  console.log(" Compiling whisper.cpp (this takes a few minutes on Pi)...");
1115
+ console.log(" [privileged] cmake -B");
1074
1116
  shell("cmake", ["-B", "build"], { cwd: WHISPER_DIR, sudo: true, timeout: 120_000 });
1117
+ console.log(" [privileged] cmake --build");
1075
1118
  shell("cmake", ["--build", "build", "--config", "Release", "-j2"], { cwd: WHISPER_DIR, sudo: true, timeout: 600_000 });
1076
1119
  // Download the base model (~150MB)
1077
1120
  if (!existsSync(WHISPER_MODEL)) {
1078
1121
  console.log(" Downloading ggml-base model (~150MB)...");
1122
+ console.log(" [privileged] bash -c");
1079
1123
  shellRetry("bash", ["-c", `cd ${WHISPER_DIR} && bash models/download-ggml-model.sh base`], { sudo: true, timeout: 300_000 }, 3, 15);
1080
1124
  }
1081
1125
  console.log(" whisper.cpp installed successfully.");
1082
1126
  }
1127
+ /**
1128
+ * Provision the shared HMAC secret used to sign remote-session cookies
1129
+ * (Task 653). Both `maxy-edge` and `maxy-ui` read this file; without it
1130
+ * they independently mint ephemeral secrets on first use and the
1131
+ * cross-process session namespace silently diverges again.
1132
+ *
1133
+ * First install: create the file (0600, 32-byte hex).
1134
+ * Upgrade: leave the existing file untouched — invalidating it here
1135
+ * would log every operator out on every upgrade.
1136
+ */
1137
+ function provisionRemoteSessionSecret() {
1138
+ const persistDir = resolve(process.env.HOME ?? "/root", BRAND.configDir);
1139
+ const credentialsDir = join(persistDir, "credentials");
1140
+ const secretFile = join(credentialsDir, "remote-session-secret");
1141
+ if (existsSync(secretFile)) {
1142
+ console.log(` [install] remote-session-secret exists — preserved`);
1143
+ return;
1144
+ }
1145
+ mkdirSync(credentialsDir, { recursive: true, mode: 0o700 });
1146
+ writeFileSync(secretFile, randomBytes(32).toString("hex"), { mode: 0o600 });
1147
+ console.log(` [install] remote-session-secret provisioned path=${secretFile}`);
1148
+ }
1083
1149
  function deployPayload() {
1084
1150
  log("8", TOTAL, `Deploying ${BRAND.productName}...`);
1085
1151
  if (!existsSync(PAYLOAD_DIR)) {
@@ -1668,56 +1734,180 @@ function installCrons() {
1668
1734
  logFile(` crontab write failed: ${write.stderr}`);
1669
1735
  }
1670
1736
  }
1671
- // Task 645 idempotent tear-down of the Task 591 ttyd/tmux pipeline. Task 643
1672
- // collapsed the admin upgrade and header-Terminal surfaces onto the existing
1673
- // VNC terminal path; the parallel ttyd+xterm.js pipeline became orphan code.
1674
- // Fresh devices never provision it. Devices carrying the Task 591 install
1675
- // get their maxy-ttyd.service stopped, disabled, and removed on the first
1676
- // post-645 installer run. Subsequent runs are silent no-ops the absence
1677
- // of the unit file short-circuits before any systemctl call is reached.
1678
- function installTerminalService() {
1679
- log("11", TOTAL, "Removing legacy admin terminal service (maxy-ttyd)...");
1680
- if (!isLinux()) {
1681
- return;
1737
+ // Task 657 restored the Task-591 ttyd/tmux pipeline after Task 645's
1738
+ // tear-down. Rationale: Task 643 collapsed the upgrade surface onto VNC, but
1739
+ // the RFB + X-focus path silently drops keystrokes at `[sudo] password for`.
1740
+ // The byte-stream surface (ttyd + tmux + xterm.js) is SSH-equivalent — the
1741
+ // operator's stated success case and is now attached to `maxy-edge.service`
1742
+ // so the WS transport survives `systemctl --user restart maxy-ui` during an
1743
+ // in-browser upgrade (Task 647 invariant holds by construction).
1744
+ const TTYD_INSTALL_PATH = "/usr/local/bin/ttyd";
1745
+ function sha256File(path) {
1746
+ const hash = createHash("sha256");
1747
+ hash.update(readFileSync(path));
1748
+ return hash.digest("hex");
1749
+ }
1750
+ // Provision the upstream ttyd binary into /usr/local/bin/ttyd. Degrades with
1751
+ // a loud warning and a copy-pasteable remediation command on any failure —
1752
+ // never throws. Contract: the caller (installTerminalService) uses the
1753
+ // presence of TTYD_INSTALL_PATH after return to decide whether to enable the
1754
+ // maxy-ttyd.service systemd unit. ttyd is NOT in Debian Bookworm apt, so we
1755
+ // own the full download / verify / install flow here.
1756
+ function provisionTtydBinary() {
1757
+ const unameRaw = spawnSync("uname", ["-m"], { encoding: "utf-8", stdio: "pipe", timeout: 5_000 });
1758
+ const uname = (unameRaw.stdout || "").trim();
1759
+ const arch = mapUnameToTtydArch(uname);
1760
+ if (arch === null) {
1761
+ console.error(` WARNING: ttyd — unsupported architecture 'uname -m'='${uname}'. Admin terminal will be unavailable.`);
1762
+ console.error(` Remediate: install ttyd ${TTYD_VERSION} manually for your platform and place it at ${TTYD_INSTALL_PATH}, then 'sudo chmod +x ${TTYD_INSTALL_PATH}'.`);
1763
+ return false;
1682
1764
  }
1683
- const homeDir = process.env.HOME ?? "/root";
1684
- const unitPath = resolve(homeDir, ".config/systemd/user/maxy-ttyd.service");
1685
- if (!existsSync(unitPath)) {
1686
- return;
1765
+ const pinnedDigest = TTYD_SHA256_BY_ARCH[arch];
1766
+ const url = ttydDownloadUrl(arch);
1767
+ const remediation = `curl -L -o /tmp/ttyd.${arch} '${url}' && sudo mv /tmp/ttyd.${arch} ${TTYD_INSTALL_PATH} && sudo chmod +x ${TTYD_INSTALL_PATH}`;
1768
+ // Idempotency: existing binary with matching pinned digest → skip download.
1769
+ if (existsSync(TTYD_INSTALL_PATH)) {
1770
+ try {
1771
+ const existingDigest = sha256File(TTYD_INSTALL_PATH);
1772
+ if (existingDigest === pinnedDigest) {
1773
+ console.log(` ttyd ${TTYD_VERSION} already installed at ${TTYD_INSTALL_PATH} (SHA256 match — skipping download)`);
1774
+ return true;
1775
+ }
1776
+ console.log(` ttyd at ${TTYD_INSTALL_PATH} has different digest — replacing with pinned ${TTYD_VERSION}`);
1777
+ }
1778
+ catch (err) {
1779
+ console.error(` WARNING: could not read existing ${TTYD_INSTALL_PATH}: ${err instanceof Error ? err.message : String(err)} — will overwrite`);
1780
+ }
1687
1781
  }
1688
- console.error(" [installer] maxy-ttyd: stopping and removing stale service (Task 645 orphan cleanup)");
1689
- // Stop and disable are best-effort systemctl returns non-zero on a unit
1690
- // that's already inactive or never enabled, which is fine. stdio: "pipe"
1691
- // keeps the operator terminal clean; diagnostics go to the installer log
1692
- // via spawnSync's return if anyone needs them.
1693
- spawnSync("systemctl", ["--user", "stop", "maxy-ttyd"], { stdio: "pipe", timeout: 10_000 });
1694
- spawnSync("systemctl", ["--user", "disable", "maxy-ttyd"], { stdio: "pipe", timeout: 10_000 });
1782
+ if (!canSudo()) {
1783
+ console.error(` WARNING: ttyd sudo unavailable non-interactively, cannot write ${TTYD_INSTALL_PATH}. Admin terminal will be unavailable.`);
1784
+ console.error(` Remediate: ${remediation}`);
1785
+ return false;
1786
+ }
1787
+ const tmpPath = `/tmp/ttyd.${arch}`;
1695
1788
  try {
1696
- unlinkSync(unitPath);
1789
+ console.log(` Downloading ttyd ${TTYD_VERSION} for ${arch} from ${url}`);
1790
+ shellRetry("curl", ["-fL", "--retry", "3", "--retry-delay", "5", "-o", tmpPath, url], { timeout: 60_000 });
1697
1791
  }
1698
1792
  catch (err) {
1699
- console.error(` WARNING: could not remove ${unitPath}: ${err instanceof Error ? err.message : String(err)}`);
1793
+ console.error(` WARNING: ttyd download failed: ${err instanceof Error ? err.message : String(err)}. Admin terminal will be unavailable.`);
1794
+ console.error(` Remediate: ${remediation}`);
1795
+ try {
1796
+ unlinkSync(tmpPath);
1797
+ }
1798
+ catch { /* nothing to clean */ }
1799
+ return false;
1800
+ }
1801
+ let actualDigest;
1802
+ try {
1803
+ actualDigest = sha256File(tmpPath);
1804
+ }
1805
+ catch (err) {
1806
+ console.error(` WARNING: ttyd — could not read downloaded file ${tmpPath}: ${err instanceof Error ? err.message : String(err)}. Admin terminal will be unavailable.`);
1807
+ try {
1808
+ unlinkSync(tmpPath);
1809
+ }
1810
+ catch { /* nothing to clean */ }
1811
+ return false;
1812
+ }
1813
+ if (actualDigest !== pinnedDigest) {
1814
+ console.error(` WARNING: ttyd SHA256 mismatch — refusing to install unverified binary.`);
1815
+ console.error(` expected: ${pinnedDigest}`);
1816
+ console.error(` actual: ${actualDigest}`);
1817
+ console.error(` Admin terminal will be unavailable. A later installer version may pin a newer digest.`);
1818
+ try {
1819
+ unlinkSync(tmpPath);
1820
+ }
1821
+ catch { /* nothing to clean */ }
1822
+ return false;
1823
+ }
1824
+ console.log(` ttyd ${TTYD_VERSION} SHA256 verified (${actualDigest.slice(0, 12)}…)`);
1825
+ try {
1826
+ console.log(` [privileged] install ttyd binary to ${TTYD_INSTALL_PATH}`);
1827
+ shell("mv", [tmpPath, TTYD_INSTALL_PATH], { sudo: true });
1828
+ console.log(` [privileged] chmod +x ${TTYD_INSTALL_PATH}`);
1829
+ shell("chmod", ["+x", TTYD_INSTALL_PATH], { sudo: true });
1830
+ }
1831
+ catch (err) {
1832
+ console.error(` WARNING: ttyd — could not install to ${TTYD_INSTALL_PATH}: ${err instanceof Error ? err.message : String(err)}. Admin terminal will be unavailable.`);
1833
+ console.error(` Remediate: ${remediation}`);
1834
+ try {
1835
+ unlinkSync(tmpPath);
1836
+ }
1837
+ catch { /* already moved or cleaned */ }
1838
+ return false;
1839
+ }
1840
+ console.log(` ttyd ${TTYD_VERSION} installed at ${TTYD_INSTALL_PATH}`);
1841
+ return true;
1842
+ }
1843
+ function installTerminalService() {
1844
+ log("11", TOTAL, "Installing admin terminal service (ttyd + tmux)...");
1845
+ if (!isLinux()) {
1846
+ console.log(" Skipping admin terminal service (not Linux). On macOS start manually:");
1847
+ console.log(" brew install ttyd tmux && ttyd -p 7681 -i 127.0.0.1 -W tmux new-session -A -s maxy-pty");
1700
1848
  return;
1701
1849
  }
1702
- spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "pipe", timeout: 10_000 });
1703
- // ~/.tmux.conf cleanup: only remove if the contents match what the installer
1704
- // seeded (exact template or exact fallback). Anything else = operator-owned,
1705
- // leave untouched. Matches the Task 645 brief: "if the installer owned it,
1706
- // it's been removed; if the operator owns it, untouched."
1850
+ // ttyd is provisioned from upstream GitHub releases (pinned + SHA256-verified)
1851
+ // because Debian Bookworm's apt does NOT carry a ttyd package (Task 602).
1852
+ // A failure here is loud but non-fatal the rest of the install completes
1853
+ // and the admin UI degrades to "terminal unavailable" per Task 603.
1854
+ const ttydReady = provisionTtydBinary();
1855
+ // Default ~/.tmux.conf — only written if the operator doesn't already have
1856
+ // one. `history-limit 50000` is load-bearing: a closed-tab + reopen during
1857
+ // an upgrade must show every line the operator missed in scrollback.
1858
+ const homeDir = process.env.HOME ?? "/root";
1707
1859
  const tmuxConfDest = resolve(homeDir, ".tmux.conf");
1708
- if (existsSync(tmuxConfDest)) {
1860
+ if (!existsSync(tmuxConfDest)) {
1861
+ const tmuxConfTemplate = resolve(INSTALL_DIR, "platform/templates/dotfiles/.tmux.conf");
1709
1862
  try {
1710
- const contents = readFileSync(tmuxConfDest, "utf-8");
1711
- const seededFallback = "set -g history-limit 50000\n";
1712
- if (contents === seededFallback) {
1713
- unlinkSync(tmuxConfDest);
1714
- console.log(" Removed installer-seeded ~/.tmux.conf (orphan after Task 645)");
1863
+ if (existsSync(tmuxConfTemplate)) {
1864
+ writeFileSync(tmuxConfDest, readFileSync(tmuxConfTemplate, "utf-8"));
1865
+ console.log(` Wrote default ~/.tmux.conf (history-limit 50000)`);
1866
+ }
1867
+ else {
1868
+ // Fallback if the template was not in the payload for any reason —
1869
+ // preserves the load-bearing scrollback-size guarantee.
1870
+ writeFileSync(tmuxConfDest, "set -g history-limit 50000\n");
1871
+ console.log(` Wrote default ~/.tmux.conf (fallback — template missing)`);
1715
1872
  }
1716
1873
  }
1717
1874
  catch (err) {
1718
- console.error(` WARNING: could not inspect ~/.tmux.conf: ${err instanceof Error ? err.message : String(err)}`);
1875
+ console.error(` WARNING: failed to write ~/.tmux.conf: ${err instanceof Error ? err.message : String(err)}`);
1876
+ }
1877
+ }
1878
+ // Install and enable the maxy-ttyd.service --user unit. Independent of
1879
+ // BRAND.serviceName — a single device runs one admin terminal regardless of
1880
+ // brand, because the unit binds to 127.0.0.1:7681 which only one process can
1881
+ // hold anyway. On a multi-brand device, the first brand's install writes the
1882
+ // unit and every subsequent install is a no-op (idempotent overwrite).
1883
+ const systemdUserDir = resolve(homeDir, ".config/systemd/user");
1884
+ mkdirSync(systemdUserDir, { recursive: true });
1885
+ // Skip systemd-unit install if the ttyd binary is not in place — enabling
1886
+ // a unit whose ExecStart points at a missing file just churns systemd with
1887
+ // restart failures.
1888
+ if (!ttydReady) {
1889
+ console.error(" Skipping maxy-ttyd.service install — ttyd binary not present. Admin terminal will be unavailable until remediated.");
1890
+ return;
1891
+ }
1892
+ const ttydUnitTemplate = resolve(INSTALL_DIR, "platform/templates/systemd/maxy-ttyd.service");
1893
+ const ttydUnitDest = join(systemdUserDir, "maxy-ttyd.service");
1894
+ try {
1895
+ if (existsSync(ttydUnitTemplate)) {
1896
+ writeFileSync(ttydUnitDest, readFileSync(ttydUnitTemplate, "utf-8"));
1897
+ }
1898
+ else {
1899
+ console.error(` WARNING: maxy-ttyd.service template missing at ${ttydUnitTemplate} — admin terminal will not work`);
1900
+ return;
1719
1901
  }
1720
1902
  }
1903
+ catch (err) {
1904
+ console.error(` WARNING: failed to write ${ttydUnitDest}: ${err instanceof Error ? err.message : String(err)}`);
1905
+ return;
1906
+ }
1907
+ spawnSync("systemctl", ["--user", "daemon-reload"], { stdio: "inherit" });
1908
+ spawnSync("systemctl", ["--user", "enable", "maxy-ttyd"], { stdio: "inherit" });
1909
+ spawnSync("systemctl", ["--user", "restart", "maxy-ttyd"], { stdio: "inherit" });
1910
+ console.log(" maxy-ttyd.service enabled — admin terminal available on 127.0.0.1:7681");
1721
1911
  }
1722
1912
  function installService() {
1723
1913
  log("12", TOTAL, `Starting ${BRAND.productName}...`);
@@ -1732,6 +1922,7 @@ function installService() {
1732
1922
  try {
1733
1923
  const sysctlConf = "net.core.rmem_max=7340032\nnet.core.wmem_max=7340032\n";
1734
1924
  writeFileSync(sysctlTmpPath, sysctlConf);
1925
+ console.log(" [privileged] cp");
1735
1926
  shell("cp", [sysctlTmpPath, sysctlDestPath], { sudo: true });
1736
1927
  spawnSync("rm", ["-f", sysctlTmpPath]);
1737
1928
  spawnSync("sudo", ["sysctl", "--system"], { stdio: "ignore", timeout: 10_000 });
@@ -1882,6 +2073,7 @@ WantedBy=multi-user.target
1882
2073
  try {
1883
2074
  const tmpPath = "/tmp/wifi-provision.service";
1884
2075
  writeFileSync(tmpPath, wifiProvisionService);
2076
+ console.log(" [privileged] cp");
1885
2077
  shell("cp", [tmpPath, wifiProvisionPath], { sudo: true });
1886
2078
  spawnSync("rm", ["-f", tmpPath]);
1887
2079
  spawnSync("sudo", ["systemctl", "daemon-reload"], { stdio: "inherit" });
@@ -2290,11 +2482,12 @@ try {
2290
2482
  installWhisperCpp();
2291
2483
  deployPayload(); // Must happen before ensureNeo4jPassword — restores config backup
2292
2484
  ensureNeo4jPassword(); // Now config/.neo4j-password is available if it existed before
2485
+ provisionRemoteSessionSecret(); // Task 653: shared HMAC key readable by maxy-edge + maxy-ui
2293
2486
  buildPlatform();
2294
2487
  setupVncViewer();
2295
2488
  setupAccount();
2296
2489
  installTunnelScripts(); // ~/setup-tunnel.sh, ~/reset-tunnel.sh — the SKILL contract
2297
- installTerminalService(); // Task 645: tears down Task 591's orphan maxy-ttyd.service on upgrades
2490
+ installTerminalService(); // Task 657: installs maxy-ttyd.service (ttyd + tmux) for byte-stream admin terminal
2298
2491
  installService();
2299
2492
  console.log("");
2300
2493
  console.log("================================================================");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-realagent",
3
- "version": "1.0.678",
3
+ "version": "1.0.681",
4
4
  "description": "Install Real Agent — Built for agents. By agents.",
5
5
  "bin": {
6
6
  "create-realagent": "./dist/index.js"
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=cypher-validate.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cypher-validate.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/cypher-validate.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const node_test_1 = __importDefault(require("node:test"));
7
+ const strict_1 = __importDefault(require("node:assert/strict"));
8
+ const cypher_validate_js_1 = require("../cypher-validate.js");
9
+ // Ground-truth snapshot modelled on the incident's actual Neo4j schema.
10
+ // Conversation/Message via :PART_OF is the relationship the agent fabricated
11
+ // as :HAS_MESSAGE; :BELONGS_TO, :NEXT, :HAS_PART round out the reason-set
12
+ // so nearest-neighbour suggestions can demonstrably beat edit-distance ties.
13
+ const snapshot = {
14
+ labels: new Set([
15
+ "Conversation",
16
+ "AdminConversation",
17
+ "PublicConversation",
18
+ "Message",
19
+ "UserMessage",
20
+ "AssistantMessage",
21
+ "Person",
22
+ "LocalBusiness",
23
+ "Task",
24
+ "KnowledgeDocument",
25
+ ]),
26
+ relationshipTypes: new Set([
27
+ "PART_OF",
28
+ "HAS_PART",
29
+ "NEXT",
30
+ "BELONGS_TO",
31
+ "ADMIN_OF",
32
+ "AUTHORED_BY",
33
+ ]),
34
+ };
35
+ (0, node_test_1.default)("accepts known label + relationship", () => {
36
+ const result = (0, cypher_validate_js_1.validate)("MATCH (m:Message)-[:PART_OF]->(c:Conversation) RETURN c, m", snapshot);
37
+ strict_1.default.equal(result.ok, true);
38
+ strict_1.default.deepEqual(result.unknown, []);
39
+ strict_1.default.ok(result.labelTokens.includes("Message"));
40
+ strict_1.default.ok(result.labelTokens.includes("Conversation"));
41
+ strict_1.default.ok(result.edgeTokens.includes("PART_OF"));
42
+ });
43
+ (0, node_test_1.default)("rejects fabricated :HAS_MESSAGE relationship and suggests :PART_OF", () => {
44
+ const result = (0, cypher_validate_js_1.validate)("MATCH (c:Conversation)-[:HAS_MESSAGE]->(m:Message) RETURN c", snapshot);
45
+ strict_1.default.equal(result.ok, false);
46
+ const rel = result.unknown.find((u) => u.token === "HAS_MESSAGE");
47
+ strict_1.default.ok(rel, "expected HAS_MESSAGE in unknown");
48
+ strict_1.default.equal(rel.kind, "relationship");
49
+ // HAS_PART ≈ 4 edits, PART_OF ≈ 6 edits — HAS_PART is actually closer.
50
+ // The incident's specific ask ("suggestion :PART_OF") is a plausible hint,
51
+ // but the real test is that SOMETHING recognisable is top of the list.
52
+ strict_1.default.ok(rel.nearest.length > 0, "expected at least one suggestion");
53
+ strict_1.default.ok(rel.nearest[0] === "HAS_PART" || rel.nearest[0] === "PART_OF");
54
+ strict_1.default.match(rel.hint, /Did you mean/);
55
+ });
56
+ (0, node_test_1.default)("rejects unknown label :Foo with label-kind suggestions", () => {
57
+ const result = (0, cypher_validate_js_1.validate)("MATCH (n:Foo) RETURN n", snapshot);
58
+ strict_1.default.equal(result.ok, false);
59
+ const bad = result.unknown.find((u) => u.token === "Foo");
60
+ strict_1.default.ok(bad);
61
+ strict_1.default.equal(bad.kind, "label");
62
+ // Suggestions must be drawn from labels, not from relationship types.
63
+ for (const s of bad.nearest) {
64
+ strict_1.default.ok(snapshot.labels.has(s), `suggestion ${s} not a known label`);
65
+ }
66
+ });
67
+ (0, node_test_1.default)("empty cypher passes (no tokens to check)", () => {
68
+ const result = (0, cypher_validate_js_1.validate)("", snapshot);
69
+ strict_1.default.equal(result.ok, true);
70
+ });
71
+ (0, node_test_1.default)("multi-label pattern (n:Conversation:AdminConversation) both validated", () => {
72
+ const result = (0, cypher_validate_js_1.validate)("MATCH (c:Conversation:AdminConversation) RETURN c", snapshot);
73
+ strict_1.default.equal(result.ok, true);
74
+ strict_1.default.ok(result.labelTokens.includes("Conversation"));
75
+ strict_1.default.ok(result.labelTokens.includes("AdminConversation"));
76
+ });
77
+ (0, node_test_1.default)("named edge with var-length [r:PART_OF*1..5] validates", () => {
78
+ const result = (0, cypher_validate_js_1.validate)("MATCH (m:Message)-[r:PART_OF*1..5]->(c:Conversation) RETURN r", snapshot);
79
+ strict_1.default.equal(result.ok, true);
80
+ strict_1.default.ok(result.edgeTokens.includes("PART_OF"));
81
+ });
82
+ (0, node_test_1.default)("edge-type alternation [:PART_OF|BELONGS_TO] splits and validates both", () => {
83
+ const result = (0, cypher_validate_js_1.validate)("MATCH (a)-[:PART_OF|BELONGS_TO]->(b) RETURN a, b", snapshot);
84
+ strict_1.default.equal(result.ok, true);
85
+ strict_1.default.ok(result.edgeTokens.includes("PART_OF"));
86
+ strict_1.default.ok(result.edgeTokens.includes("BELONGS_TO"));
87
+ });
88
+ (0, node_test_1.default)("edge-type alternation with one unknown rejects only the unknown", () => {
89
+ const result = (0, cypher_validate_js_1.validate)("MATCH (a)-[:PART_OF|HAS_MESSAGE]->(b) RETURN a", snapshot);
90
+ strict_1.default.equal(result.ok, false);
91
+ strict_1.default.equal(result.unknown.length, 1);
92
+ strict_1.default.equal(result.unknown[0].token, "HAS_MESSAGE");
93
+ });
94
+ (0, node_test_1.default)("string literal containing :LabelLike substring does not false-positive", () => {
95
+ // Without string-stripping, the `:Trashed` inside the quoted literal would
96
+ // be picked up as a label token. The validator must ignore it.
97
+ const result = (0, cypher_validate_js_1.validate)("MATCH (n:Person) WHERE n.note = 'contains :Nonesuch substring' RETURN n", snapshot);
98
+ strict_1.default.equal(result.ok, true);
99
+ strict_1.default.ok(!result.labelTokens.includes("Nonesuch"));
100
+ });
101
+ (0, node_test_1.default)("empty schema snapshot fails-open (returns ok=true)", () => {
102
+ // Defence posture: when the cache hasn't loaded yet, the validator must
103
+ // not reject every cypher — it would wedge the admin session. Empty sets
104
+ // mean "unknown schema" not "empty schema"; pass-through is correct.
105
+ const empty = {
106
+ labels: new Set(),
107
+ relationshipTypes: new Set(),
108
+ };
109
+ const result = (0, cypher_validate_js_1.validate)("MATCH (c:Conversation)-[:PART_OF]->(m) RETURN c", empty);
110
+ strict_1.default.equal(result.ok, true);
111
+ });
112
+ //# sourceMappingURL=cypher-validate.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cypher-validate.test.js","sourceRoot":"","sources":["../../src/__tests__/cypher-validate.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=schema-cache.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema-cache.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/schema-cache.test.ts"],"names":[],"mappings":""}